/**
 * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information regarding copyright ownership. Apereo
 * licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the License at the
 * following location:
 *
 * <p>http://www.apache.org/licenses/LICENSE-2.0
 *
 * <p>Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apereo.portal.xml.stream;

import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EntityDeclaration;
import javax.xml.stream.events.EntityReference;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.EventReaderDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base for XMLEventReader that implements the {@link #getElementText()} and {@link #nextTag()} APIs
 * in a way that is agnostic from the rest of the XMLEventReader implementation. Both will use the
 * subclasses {@link #internalNextEvent()} as the exclusive way to read events.
 */
public abstract class BaseXMLEventReader extends EventReaderDelegate {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    private XMLEvent previousEvent;

    public BaseXMLEventReader(XMLEventReader reader) {
        super(reader);
    }

    /** Subclass's version of {@link #nextEvent()}, called by {@link #next()} */
    protected abstract XMLEvent internalNextEvent() throws XMLStreamException;

    /** @return The XMLEvent returned by the last call to {@link #internalNextEvent()} */
    protected final XMLEvent getPreviousEvent() {
        return this.previousEvent;
    }

    @Override
    public final XMLEvent nextEvent() throws XMLStreamException {
        this.previousEvent = this.internalNextEvent();
        return this.previousEvent;
    }

    /* (non-Javadoc)
     * @see java.util.Iterator#next()
     */
    @Override
    public final Object next() {
        try {
            return this.nextEvent();
        } catch (XMLStreamException e) {
            return null;
        }
    }

    /* (non-Javadoc)
     * @see javax.xml.stream.XMLEventReader#getElementText()
     */
    @Override
    public final String getElementText() throws XMLStreamException {
        XMLEvent event = this.previousEvent;
        if (event == null) {
            throw new XMLStreamException(
                    "Must be on START_ELEMENT to read next text, element was null");
        }
        if (!event.isStartElement()) {
            throw new XMLStreamException(
                    "Must be on START_ELEMENT to read next text", event.getLocation());
        }

        final StringBuilder text = new StringBuilder();
        while (!event.isEndDocument()) {
            switch (event.getEventType()) {
                case XMLStreamConstants.CHARACTERS:
                case XMLStreamConstants.SPACE:
                case XMLStreamConstants.CDATA:
                    {
                        final Characters characters = event.asCharacters();
                        text.append(characters.getData());
                        break;
                    }
                case XMLStreamConstants.ENTITY_REFERENCE:
                    {
                        final EntityReference entityReference = (EntityReference) event;
                        final EntityDeclaration declaration = entityReference.getDeclaration();
                        text.append(declaration.getReplacementText());
                        break;
                    }
                case XMLStreamConstants.COMMENT:
                case XMLStreamConstants.PROCESSING_INSTRUCTION:
                    {
                        // Ignore
                        break;
                    }
                default:
                    {
                        throw new XMLStreamException(
                                "Unexpected event type '"
                                        + XMLStreamConstantsUtils.getEventName(event.getEventType())
                                        + "' encountered. Found event: "
                                        + event,
                                event.getLocation());
                    }
            }

            event = this.nextEvent();
        }

        return text.toString();
    }

    /* (non-Javadoc)
     * @see javax.xml.stream.XMLEventReader#nextTag()
     */
    @Override
    public final XMLEvent nextTag() throws XMLStreamException {
        XMLEvent event = this.nextEvent();
        while ((event.isCharacters() && event.asCharacters().isWhiteSpace())
                || event.isProcessingInstruction()
                || event.getEventType() == XMLStreamConstants.COMMENT) {

            event = this.nextEvent();
        }

        if (!event.isStartElement() && event.isEndElement()) {
            throw new XMLStreamException(
                    "Unexpected event type '"
                            + XMLStreamConstantsUtils.getEventName(event.getEventType())
                            + "' encountered. Found event: "
                            + event,
                    event.getLocation());
        }

        return event;
    }
}
